package com.saturn.titan.util;

import java.util.HashMap;
import java.util.Map;
import java.util.regex.Pattern;

public class LinkNormalizer {

	static final Map<Pattern, String> ESCAPE_PATTERNS = new HashMap<Pattern, String>();

	static {
		ESCAPE_PATTERNS.put(Pattern.compile("\\s"), "%20");
		ESCAPE_PATTERNS.put(Pattern.compile("\\^"), "%5E");
		ESCAPE_PATTERNS.put(Pattern.compile("\""), "%22");
		ESCAPE_PATTERNS.put(Pattern.compile("'"), "%27");
		ESCAPE_PATTERNS.put(Pattern.compile("`"), "%60");
		ESCAPE_PATTERNS.put(Pattern.compile("\\["), "%5B");
		ESCAPE_PATTERNS.put(Pattern.compile("\\]"), "%5D");
		ESCAPE_PATTERNS.put(Pattern.compile("\\{"), "%7B");
		ESCAPE_PATTERNS.put(Pattern.compile("\\}"), "%7D");
	}

	static final Map<Pattern, String> UNESCAPE_PATTERNS = new HashMap<Pattern, String>();

	static {
		UNESCAPE_PATTERNS.put(Pattern.compile("%20"), " ");
		UNESCAPE_PATTERNS.put(Pattern.compile("%5E"), "^");
		UNESCAPE_PATTERNS.put(Pattern.compile("%22"), "\"");
		UNESCAPE_PATTERNS.put(Pattern.compile("%27"), "'");
		UNESCAPE_PATTERNS.put(Pattern.compile("%60"), "`");
		UNESCAPE_PATTERNS.put(Pattern.compile("%5B"), "[");
		UNESCAPE_PATTERNS.put(Pattern.compile("%5D"), "]");
		UNESCAPE_PATTERNS.put(Pattern.compile("%7B"), "{");
		UNESCAPE_PATTERNS.put(Pattern.compile("%7D"), "}");
	}

	static protected final Map<Pattern, String> URL_REPLACEMENTS = new HashMap<Pattern, String>();

	static {
		/**
		 * Replace tab, line feed, and carriage-return characters.
		 */
		URL_REPLACEMENTS.put(Pattern.compile("[\t\n\r]"), "");

	}

	static protected final Map<Pattern, String> PATH_REPLACEMENTS = new HashMap<Pattern, String>();

	static {
		/**
		 * Replace any "\" with "/"
		 */
		PATH_REPLACEMENTS.put(Pattern.compile("\\\\"), "/");

		/**
		 * Replace "/path/../index.html" with "/index.html"
		 */
		PATH_REPLACEMENTS.put(Pattern.compile("/[^/]*/\\.\\./"), "/");

		/**
		 * For URL starts with "/", replace "/../index.html" with "/index.html"
		 */
		PATH_REPLACEMENTS.put(Pattern.compile("^/([\\.]{1,2}/)*"), "/");

		/**
		 * Replace any "//" or "/./" with "/"
		 */
		PATH_REPLACEMENTS.put(Pattern.compile("/([\\.]?/)*"), "/");

	}

	/**
	 * Support full URL (e.g. http://www.apache.org/style/style.css) and partial URI (e.g. /style/style.css)
	 */
	public String normalize(String url) {
		if (url == null)
			throw new IllegalArgumentException("url is null");
		url = url.trim();
		for (Pattern pattern : URL_REPLACEMENTS.keySet()) {
			url = pattern.matcher(url).replaceAll(URL_REPLACEMENTS.get(pattern));
		}

		int queryStringIndex = url.indexOf('?');
		int pageAnchorIndex = url.indexOf('#');
		String hostTest = queryStringIndex != -1 ? url.substring(0, queryStringIndex) : url;
		int hostIndex = hostTest.indexOf("://") != -1 ? hostTest.indexOf("://") + 3 : -1;
		int pathIndex = hostIndex != -1 ? url.indexOf('/', hostIndex) : 0; // schema must present if use host

		if (pathIndex > queryStringIndex && queryStringIndex > 0) {
			pathIndex = -1;
		}
		int pathEndIndex = StrictMath.min(pageAnchorIndex != -1 ? pageAnchorIndex : url.length(), queryStringIndex != -1 ? queryStringIndex : url.length());

		String schemaHostPort = pathIndex != -1 ? url.substring(0, pathIndex) : url.substring(0, url.length());
		String path = pathIndex != -1 ? url.substring(pathIndex, pathEndIndex) : "";
		String suffix = pathEndIndex != url.length() ? url.substring(pathEndIndex, url.length()) : "";

		// lowercase & trim port
		schemaHostPort = schemaHostPort.toLowerCase();
		if (schemaHostPort.startsWith("http:") && schemaHostPort.endsWith(":80"))
			schemaHostPort = schemaHostPort.substring(0, schemaHostPort.length() - ":80".length());
		if (schemaHostPort.startsWith("https:") && schemaHostPort.endsWith(":443"))
			schemaHostPort = schemaHostPort.substring(0, schemaHostPort.length() - ":443".length());

		// apply pattens
		if (path != null && !"".equals(path))
			for (Pattern pattern : PATH_REPLACEMENTS.keySet()) {
				path = pattern.matcher(path).replaceAll(PATH_REPLACEMENTS.get(pattern));
			}
		else {
			path = "/";
		}

		String normalized = schemaHostPort + path + suffix;
		return normalized;
	}

	public String escape(String url) {
		for (Pattern pattern : ESCAPE_PATTERNS.keySet()) {
			url = pattern.matcher(url).replaceAll(ESCAPE_PATTERNS.get(pattern));
		}
		return url;
	}

	public String unescape(String url) {
		for (Pattern pattern : UNESCAPE_PATTERNS.keySet()) {
			url = pattern.matcher(url).replaceAll(UNESCAPE_PATTERNS.get(pattern));
		}
		return url;
	}

}
